export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

export interface ITree {
  title: string;
  key: string; // 每一个node需要有唯一标识
  expanded?: boolean;
  checked?: boolean;
  hidden?: boolean;
  children?: ITree[];
  parent?: ITree;
  disabled?: boolean;
  halfChecked?: boolean;
}

export interface ITreeLike<T> {
  children?: T[];
}

export const getKeys = <T extends {}>(o: T): Array<keyof T> => {
  return Object.keys(o) as Array<keyof T>;
};

export const nope = () => undefined;

export const omit = <T extends { [key: string]: any }>(object: T, array: string[]): T => {
  // const mapObject = { ...object };
  const mapObject = Object.assign({}, object);
  array.forEach((key: string) => {
    delete mapObject[key];
  });
  return mapObject;
};

/**
 * 遍历树节点
 * @param treeNode
 * @param traverse
 */
export const traverseTree = (treeNode: ITree, traverse: (treeNode: ITree) => void) => {
  traverse(treeNode);
  if (treeNode.children) {
    treeNode.children.forEach((node) => traverseTree(node, traverse));
  }
};

/**
 * 获取树的叶子节点
 * @param treeNode
 */
export const getTreeLeafs = (treeNode: ITree) => {
  const nodes: ITree[] = [];
  traverseTree(treeNode, (node) => {
    if (!node.children || !node.children.length) {
      nodes.push(node);
    }
  });
  return nodes;
};

/**
 * 获取选中的叶子节点
 * @param treeNode
 */
export const getTreeCheckedLeafs = (treeNode: ITree) => {
  return getTreeLeafs(treeNode).filter((node) => node.checked);
};

/**
 * 根据规则，生成新的树
 * @param treeNode
 * @param mapFunction
 */
export const treeMap = <T extends ITreeLike<T>>(treeNode: T, mapFunction?: (node: T) => ITree): ITree => {
  if (!mapFunction) {
    return (treeNode as any) as ITree;
  }
  const newNode = mapFunction(treeNode);
  if (treeNode.children) {
    newNode.children = treeNode.children.map((node) => mapFunction(node));
  }
  return newNode;
};

/**
 * 根据规则生成新的树，从叶子节点开始遍历
 * @param treeData
 * @param func
 */
const treeDataMap = (treeData: ITree[], func: (node: ITree) => ITree): ITree[] => {
  return treeData.map((node) => {
    if (node.children && node.children.length) {
      return func({
        ...node,
        children: treeDataMap(node.children, func),
      });
    }
    return func(node);
  });
};

/**
 * 获取treeData中选中的节点
 * @param treeData
 */
export const getTreeDataCheckedLeafs = (treeData: ITree[]) => {
  return treeData.reduce((results: ITree[], node) => {
    return results.concat(getTreeCheckedLeafs(node));
  }, []);
};

/**
 * 清除treeData中所有选中的节点
 * @param treeData
 */
export const clearTreeDataCheckedState = (treeData: ITree[]) => {
  return treeDataMap(treeData, (node) => {
    if (node.checked) {
      return {
        ...node,
        checked: false,
      };
    }
    return node;
  });
};

/**
 * 获取树的深度
 */
export const getTreeDataDepth = (treeData?: ITree[]): number => {
  if (!treeData || !treeData.length) {
    return 0;
  }
  return Math.max(...treeData.map((tree) => getTreeDataDepth(tree.children))) + 1;
};

/**
 * 判断treeData是否有多层
 */
export const isTreeDataHasMultiLevel = (treeData: ITree[]) => {
  return !!treeData.find((tree) => !!(tree.children && tree.children.length));
};

type CheckedType = 'LATEST_NODE' | 'LEAFS_ONLY' | 'ALL';

/**
 * 获取treeData中选中的树干节点
 * @param treeData
 */
export const getTreeDataCheckedNodes = (treeData: ITree[], checkedType: CheckedType = 'LATEST_NODE'): ITree[] => {
  if (checkedType === 'LEAFS_ONLY') {
    return getTreeDataCheckedLeafs(treeData);
  }

  return treeData.reduce((results: ITree[], node) => {
    if (node.checked) {
      if (checkedType === 'LATEST_NODE') {
        return results.concat(node);
      }
      if (checkedType === 'ALL') {
        results.push(node);
      }
    }
    return results.concat(getTreeDataCheckedNodes(node.children || [], checkedType));
  }, []);
};

/**
 * 判断滚动条是否出现
 * @param dom
 */
export const isScrollBarVisible = (dom: HTMLElement) => {
  return dom.scrollHeight > dom.clientHeight;
};

export const getPageBoundingClientRect = (dom: HTMLElement) => {
  const rect = dom.getBoundingClientRect();
  return {
    left: rect.left + window.pageXOffset,
    top: rect.top + window.pageYOffset,
    right: rect.right + window.pageXOffset,
    bottom: rect.bottom + window.pageYOffset,
    width: dom.clientWidth,
    height: dom.clientHeight,
  };
};
